package com.flame.shapeville.config;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * Configuration class responsible for initializing and managing database resource files.
 * <p>
 * This class ensures that required database files exist in the target directory.
 * If they do not exist, it copies them from the application's resources.
 *
 * @author Flame Dev Team
 * @version 1.0
 * @since 2025-05-16
 */
public class DbConfig {

    /**
     * Resource directory path inside the application jar where database files are stored.
     */
    private final static String RESOURCE_DIR = "/db";

    /**
     * Target directory name where database files should be copied to.
     */
    private final static String TARGET_DIR = "db";

    /**
     * Path to the resource list file that contains names of all required database files.
     */
    private final static String RESOURCE_TXT = "/db/resource.txt";

    /**
     * Initializes the database configuration by ensuring the existence of the target directory
     * and copying necessary resource files if they are missing.
     */
    public static void init() {
        Path targetDirPath = ensureDirExist();
        checkFileExist(targetDirPath);
    }

    /**
     * Ensures the target directory exists.
     * If not, creates the directory structure.
     *
     * @return the path to the ensured target directory
     */
    private static Path ensureDirExist() {
        Path workDirPath = Paths.get(System.getProperty("user.dir"));
        Path targetDirPath = workDirPath.getParent().resolve(TARGET_DIR);
        File targetDir = targetDirPath.toFile();
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }
        return targetDirPath;
    }

    /**
     * Checks whether all required resource files exist in the target directory.
     * If any file is missing, triggers a full copy of resources.
     *
     * @param targetDirPath the path to the target directory
     */
    private static void checkFileExist(Path targetDirPath) {
        File[] targetFiles = targetDirPath.toFile().listFiles();
        if (targetFiles == null || targetFiles.length == 0) {
            copyResource(targetDirPath);
        } else {
            Set<String> fileNames = new HashSet<>();
            for (File file : targetFiles) {
                fileNames.add(file.getName());
            }
            Map<String, InputStream> resources = getResources();
            for (String resourceName : resources.keySet()) {
                if (!fileNames.contains(resourceName)) {
                    copyResource(targetDirPath);
                    break;
                }
            }
        }
    }

    /**
     * Copies all resource files from the application's jar to the target directory.
     *
     * @param targetDirPath the path to the target directory
     */
    private static void copyResource(Path targetDirPath) {
        Map<String, InputStream> resources = getResources();
        File targetDir = targetDirPath.toFile();
        for (Map.Entry<String, InputStream> streamEntry : resources.entrySet()) {
            try (FileOutputStream fos = new FileOutputStream(targetDir + "/" + streamEntry.getKey())) {
                fos.write(streamEntry.getValue().readAllBytes());
                streamEntry.getValue().close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * Reads the resource list file and returns a map of file names to input streams.
     * These represent the database files to be copied during initialization.
     *
     * @return a map containing resource file names and their corresponding input streams
     */
    private static Map<String, InputStream> getResources() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(DbConfig.class.getResourceAsStream(RESOURCE_TXT))));
            Map<String, InputStream> resources = new HashMap<>();
            String line;
            while ((line = reader.readLine()) != null) {
                resources.put(line, DbConfig.class.getResourceAsStream(RESOURCE_DIR + "/" + line));
            }
            return resources;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
